package com.itsu.job.rpc.basic;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Copy from : https://github.com/xuxueli/xxl-rpc
 *
 * @author jerry
 */
public class EmbedServer {

    private static final Logger logger = LoggerFactory.getLogger(EmbedServer.class);

    private EmbedHttpServerHandler embedHttpServerHandler = null;

    private int httpMaxContentLength = 5 * 1024 * 1024;

    private int coreSize = Runtime.getRuntime().availableProcessors();

    private int maxPoolSize = Runtime.getRuntime().availableProcessors() * 2;

    private long keepAliveTime = 60L;

    private int workQueueSize = 2000;

    private Channel channel = null;

    public EmbedServer() {
    }

    public EmbedServer(EmbedHttpServerHandler embedHttpServerHandler) {
        this.embedHttpServerHandler = embedHttpServerHandler;
    }

    public EmbedServer(int httpMaxContentLength, int coreSize, int maxPoolSize, long keepAliveTime, int workQueueSize) {
        this.httpMaxContentLength = httpMaxContentLength;
        this.coreSize = coreSize;
        this.maxPoolSize = maxPoolSize;
        this.keepAliveTime = keepAliveTime;
        this.workQueueSize = workQueueSize;
    }

    protected ThreadPoolExecutor defaultThreadPoolExecutor() {
        return new ThreadPoolExecutor(
                this.coreSize,
                this.maxPoolSize,
                this.keepAliveTime,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(workQueueSize),
                r -> new Thread(r, " EmbedServer bizThreadPool-" + r.hashCode()),
                (r, executor) -> {
                    throw new RuntimeException(" EmbedServer bizThreadPool is EXHAUSTED!");
                });

    }

    public void start(final int port) {

        // param
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();


        try {
            // start server
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel channel) {
                            channel.pipeline()
                                    .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle
                                    .addLast(new HttpServerCodec())
                                    .addLast(new HttpObjectAggregator(httpMaxContentLength));  // merge request & reponse to FULL
                            if (embedHttpServerHandler != null) {
                                channel.pipeline().addLast(embedHttpServerHandler);
                            } else {
                                channel.pipeline().addLast(new EmbedHttpServerHandler("", defaultThreadPoolExecutor()) {
                                    @Override
                                    protected Object doDelete(String uri, String requestData) {
                                        return this.getClass().getName() + " do delete";
                                    }

                                    @Override
                                    protected Object doPut(String uri, String requestData) {
                                        return this.getClass().getName() + " do put";
                                    }

                                    @Override
                                    protected Object doPost(String uri, String requestData) {
                                        return this.getClass().getName() + "do post";
                                    }

                                    @Override
                                    protected Object doGet(String uri) {
                                        return this.getClass().getName() + "do get";
                                    }

                                    @Override
                                    protected void handleException(Throwable cause) {
                                        cause.printStackTrace();
                                    }
                                });
                            }
                        }
                    })
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // bind
            ChannelFuture future = bootstrap.bind(port).sync();

            logger.info(">>>>>>>>>>> remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);

            channel = future.channel();
            // start registry
            startHook();

            // wait util stop
            future.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            logger.info(">>>>>>>>>>>  remoting server stop.");
        } finally {
            // stop
            try {
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }

    }

    public void stop() throws Exception {
        // stop registry
        stopHook();
        if (channel != null) {
            channel.close();
        }
        logger.info(">>>>>>>>>>> remoting server destroy success.");
    }

    // ---------------------- registry ----------------------
    protected void startHook() {
    }

    protected void stopHook() {
    }


    public EmbedHttpServerHandler getEmbedHttpServerHandler() {
        return embedHttpServerHandler;
    }

    public void setEmbedHttpServerHandler(EmbedHttpServerHandler embedHttpServerHandler) {
        this.embedHttpServerHandler = embedHttpServerHandler;
    }

    public int getHttpMaxContentLength() {
        return httpMaxContentLength;
    }

    public void setHttpMaxContentLength(int httpMaxContentLength) {
        this.httpMaxContentLength = httpMaxContentLength;
    }

    public int getCoreSize() {
        return coreSize;
    }

    public void setCoreSize(int coreSize) {
        this.coreSize = coreSize;
    }

    public int getMaxPoolSize() {
        return maxPoolSize;
    }

    public void setMaxPoolSize(int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }

    public long getKeepAliveTime() {
        return keepAliveTime;
    }

    public void setKeepAliveTime(long keepAliveTime) {
        this.keepAliveTime = keepAliveTime;
    }

    public long getWorkQueueSize() {
        return workQueueSize;
    }

    public void setWorkQueueSize(int workQueueSize) {
        this.workQueueSize = workQueueSize;
    }

}
